﻿#region Using Statements
using Microsoft.Xna.Framework;
#endregion

namespace FierceWar
{
    public class ChaseCamera
    {
        #region Chased object properties (set externally each frame)
        private Vector3 _chasePosition;
        public Vector3 ChasePosition
        {
            get
            {
                return _chasePosition;
            }
            set
            {
                _chasePosition = value;
            }
        }

        private Vector3 _chaseDirection;
        public Vector3 ChaseDirection
        {
            get
            {
                return _chaseDirection;
            }
            set
            {
                _chaseDirection = value;
            }
        }

        private Vector3 _up;
        public Vector3 Up
        {
            get
            {
                return _up;
            }
            set
            {
                _up = value;
            }
        }
        #endregion

        #region Desired camera positioning (set when creating camera or changing view)
        private Vector3 _desiredPositionOffset = new Vector3(0.0f, 2.0f, 2.0f);
        public Vector3 DesiredPositionOffset
        {
            get
            {
                return _desiredPositionOffset;
            }
            set
            {
                _desiredPositionOffset = value;
            }
        }

        private Vector3 _desiredPosition;
        public Vector3 DesiredPosition
        {
            get
            {
                UpdateWorldPosition();
                return _desiredPosition;
            }
        }

        private Vector3 _lookAtOffset = new Vector3(0.0f, 3.0f, 0.0f);
        public Vector3 LookAtOffset
        {
            get
            {
                return _lookAtOffset;
            }
            set
            {
                _lookAtOffset = value;
            }
        }

        private Vector3 _lookAt;
        public Vector3 LookAt
        {
            get
            {
                UpdateWorldPosition();
                return _lookAt;
            }
        }
        #endregion

        #region Camera physic properties (set when creating camera)
        private float _stiffness = 1800.0f;
        public float Stiffness
        {
            get
            {
                return _stiffness;
            }
            set
            {
                _stiffness = value;
            }
        }

        private float _damping = 600.0f;
        public float Damping
        {
            get
            {
                return _damping;
            }
            set
            {
                _damping = value;
            }
        }

        private float _mass = 50.0f;
        public float Mass
        {
            get
            {
                return _mass;
            }
            set
            {
                _mass = value;
            }
        }
        #endregion

        #region Current camera properties (updated by camera physic properties)
        private Vector3 _velocity;
        public Vector3 Velocity
        {
            get
            {
                return _velocity;
            }
            set
            {
                _velocity = value;
            }
        }

        private Vector3 _position;
        public Vector3 Position
        {
            get
            {
                return _position;
            }
            set
            {
                _position = value;
            }
        }

		public bool IsFPSCamera = false;
        #endregion

        #region Perspective properties
        private float _aspectRatio = 4.0f / 3.0f;
        public float AspectRatio
        {
            get
            {
                return _aspectRatio;
            }
            set
            {
                _aspectRatio = value;
            }
        }

        private float _fieldOfView = MathHelper.ToRadians(45.0f);
        public float FieldOfView
        {
            get
            {
                return _fieldOfView;
            }
            set
            {
                _fieldOfView = value;
            }
        }

        private float _nearPlaneDistance = 1.0f;
        public float NearPlaneDistance
        {
            get
            {
                return _nearPlaneDistance;
            }
            set
            {
                _nearPlaneDistance = value;
            }
        }

        private float _farPlaneDistance = 100000.0f;
        public float FarPlaneDistance
        {
            get
            {
                return _farPlaneDistance;
            }
            set
            {
                _farPlaneDistance = value;
            }
        }
        #endregion

        #region Matrices
        private Matrix _view;
        public Matrix View
        {
            get
            {
                return _view;
            }
        }

        private Matrix _projection;
        public Matrix Projection
        {
            get
            {
                return _projection;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Stop motion.
        /// </summary>
        public void Reset()
        {
            UpdateWorldPosition();

            _position = _desiredPosition;

            _velocity = Vector3.Zero;

            UpdateMatrices();
        }

        private void UpdateWorldPosition()
        {
            Matrix transform = Matrix.Identity;
            transform.Forward = _chaseDirection;
            transform.Up = _up;
            transform.Right = Vector3.Cross(_chaseDirection, _up);

            _desiredPosition = _chasePosition +
                Vector3.TransformNormal(_desiredPositionOffset, transform);
            _lookAt = _chasePosition +
                Vector3.TransformNormal(_lookAtOffset, transform);
        }

        private void UpdateMatrices()
        {
            _view = Matrix.CreateLookAt(_position, _lookAt, _up);
            _projection = Matrix.CreatePerspectiveFieldOfView(
                _fieldOfView, _aspectRatio,
                _nearPlaneDistance, _farPlaneDistance);
        }

        public void Update(GameTime gameTime)
        {
            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

            UpdateWorldPosition();

            Vector3 stretch = _position - _desiredPosition;
            Vector3 force = (-_stiffness * stretch) + (-_damping * _velocity);

            // Apply acceleration.
            Vector3 acceleration = force / _mass;
            _velocity += acceleration * elapsedTime;

            // Apply velocity.
            _position += _velocity * elapsedTime;

            UpdateMatrices();
        }
        #endregion
    }
}
